package cn.demoncat.util.lang;

import cn.demoncat.util.exception.BizRuntimeException;
import cn.demoncat.util.web.TokenUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * JWT 工具
 * 
 * @author 延晓磊
 * @since 2024年01月10日
 */
public class JwtUtil {

    // 签名密钥，默认TokenUtil.KEY_CODE
    private static String SECRET = null;

    /**
     * 设置密钥
     */
    public static void setSecret(String secret){
        SECRET = secret;
    }

    /**
     * 获取密钥
     *
     * @return  默认TokenUtil.KEY_CODE，可以通过 setSecret 进行全局变更
     */
    public static String getSecret(){
        return SECRET == null ? TokenUtil.getKeyCode() : SECRET;
    }

    /**
     * 生成 Token
     *
     * @param id        用户标识（写入 claim.jti）
     * @param expires   到期时间（写入 claim.expires）
     * @return
     */
    public static String build(String id, LocalDateTime expires){
        // Builder
        JWTCreator.Builder builder = JWT.create();
        // Payload：相当于 builder.withClaim('jti', id)
        builder.withJWTId(id);
        // 过期时间：相当于 builder.withClaim('exp', expires)
        builder.withExpiresAt(LocalDateUtil.getInstant(expires));
        // 签名
        return builder.sign(Algorithm.HMAC256(getSecret()));
    }

    /**
     * 校验Token
     *
     * @param token
     * @return 解析结果：getExpiresAt() 获取结束时间，getClaim("key").asString() 获取载荷
     */
    public static DecodedJWT verify(String token){
        try {
            return JWT.require(Algorithm.HMAC256(getSecret())).build().verify(token);
        }catch (SignatureVerificationException | AlgorithmMismatchException e){
            throw new BizRuntimeException("Token签名错误", e);
        }catch (TokenExpiredException e){
            throw new BizRuntimeException("Token过期", e);
        }catch (InvalidClaimException e){
            throw new BizRuntimeException("Token内容错误", e);
        }
    }

    /**
     * 校验Token
     *
     * @param token
     * @param id    用户标识（对比 claim.jti）
     *
     * @return 解析结果：getExpiresAt() 获取结束时间，getClaim("key").asString() 获取载荷
     */
    public static DecodedJWT verify(String token, String id){
        DecodedJWT jwt = verify(token);
        if (!id.equals(jwt.getId())) {
            throw new BizRuntimeException("Token内容不匹配");
        }
        return jwt;
    }

    /**
     * 校验Token，返回ID
     *
     * @param token
     * @return 用户标识（claim.jti）
     */
    public static String getId(String token){
        return verify(token).getId();
    }

    /**
     * 构建 OAuth2 Token
     *
     * @param id        用户标识（写入 claim.jti）
     * @param expires   到期秒数：access_token
     * @param refreshExpires  到期秒数：refresh_token
     * @return
     */
    public static Token buildToken(String id, int expires, int refreshExpires){
        LocalDateTime now = LocalDateTime.now();
        Token token = new Token();
        token.setAccess_token(build(id, now.plusSeconds(expires)));
        token.setRefresh_token(build(id, now.plusSeconds(refreshExpires)));
        token.setTime(LocalDateUtil.toStr(now));
        token.setExpires_in(expires);
        token.setRefresh_expires_in(refreshExpires);
        return token;
    }

    /**
     * 刷新 OAuth2 Token
     *
     * @param refreshToken
     * @param id        用户标识（对比并写入 claim.jti）
     * @param expires   到期秒数：access_token
     * @param refreshExpires  到期秒数：refresh_token
     * @return 重新生成
     */
    public static Token refreshToken(String refreshToken, String id, int expires, int refreshExpires){
        // 校验Token
        DecodedJWT jwt = verify(refreshToken, id);
        // 重新生成
        return buildToken(id, expires, refreshExpires);
    }

    /**
     * OAuth2 Token
     *
     * @author 延晓磊
     * @since 2024年01月10日
     */
    @Data
    public static class Token{

       // Token
       private String access_token;
       private String refresh_token;
       // 当前时间
       private String time;
       // 过期时间（秒）
       private Integer expires_in;
       private Integer refresh_expires_in;
    }

}